﻿namespace NTLib.Core.Factories
{
    using NTLib.Core.Component;
    using NTLib.Core.Reflection;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading.Tasks;
    using NTLib.Core.Attributes;
    using NTLib.Core.Pool;
    using Exceptions.Component;
    using Configuration;

    /// <summary>
    /// Factory that managed the 
    /// </summary>
    /// <typeparam name="T">IComponent type that will be instanciate</typeparam>
    public class ComponentFactory<T> : BaseConfigurable
       where T : IComponent
    {
        #region fields

        private readonly Dictionary<Guid, IComponent> _concurrentComponent;
        private readonly Dictionary<Guid, Type> _componentTypes;
        private readonly Dictionary<Type, System.Reflection.MethodInfo> _poolCreator;
        private readonly TypePool _pool;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentFactory{T}"/> class.
        /// </summary>
        public ComponentFactory()
        {
            this._concurrentComponent = new Dictionary<Guid, IComponent>();
            this._componentTypes = new Dictionary<Guid, Type>();
            this._poolCreator = new Dictionary<Type, System.Reflection.MethodInfo>();
            this._pool = new TypePool();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a specify instance from the guid id
        /// </summary>
        /// <param name="id">The type identifier.</param>
        /// <exception cref="NTLib.Core.Exceptions.Component.ComponentNotRegisteredException">if the guid id is not registred.</exception>
        public T Create(Guid id)
        {
            Type toCreate = null;
            if (this._componentTypes.TryGetValue(id, out toCreate))
            {
                IComponent singleton = null;
                if (this._concurrentComponent.TryGetValue(id, out singleton))
                    return (T)singleton;

                if (toCreate.IsOrInheritOf(typeof(IPoolItem)))
                    return (T)this._poolCreator[toCreate].Invoke(this._pool, TypeExtensions.EmptyParameters);

                return (T)Activator.CreateInstance(toCreate);
            }
            throw new ComponentNotRegisteredException();
        }

        /// <summary>
        /// Clears this instance.
        /// Remove all the instance registered
        /// </summary>
        public void Clear()
        {
            this._componentTypes.Clear();
            this._concurrentComponent.Clear();
            this._poolCreator.Clear();
        }

        /// <summary>
        /// Called to Loads the configuration.
        /// </summary>
        /// <param name="configs">The configuration values.</param>
        /// <exception cref="NotImplementedException"></exception>
        protected override void OnLoadConfig(ConfigurationCollection configs)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Called to reset the configuration.
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        protected override void OnResetConfig()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Registers this type.
        /// </summary>
        /// <typeparam name="U"></typeparam>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Guid Register<U>()
            where U : T
        {
            return RegisterImpl(typeof(U));
        }

        /// <summary>
        /// Registers this type and check if the type correspond to the specific condition.
        /// </summary>
        /// <typeparam name="U"></typeparam>
        public Guid Register(Type type)
        {
            if (!type.IsOrInheritOf(typeof(IComponent)))
                throw new InvalidCastException("Attending a IComponent type");
            return RegisterImpl(type);
        }

        /// <summary>
        /// Registers this type.
        /// </summary>
        /// <param name="T">The t.</param>
        /// <returns></returns>
        private Guid RegisterImpl(Type T)
        {
            var guid = T.GetMandatoryAttribute<GuidAttribute>();

            if (this._componentTypes.ContainsKey(guid.Value))
                return guid.Value;

            if (T.HasAttr<ConcurrentComponentAttribute>())
                this._concurrentComponent.Add(guid.Value, (IComponent)Activator.CreateInstance(T));
            else
            {
                var poolItemType = typeof(IPoolItem);
                if (T.IsOrInheritOf(poolItemType))
                {
                    if (!this._poolCreator.ContainsKey(T))
                    {
                        var mth = TypeExtensions.GetDeclaredMethod<TypePool>(p => p.Get<TypePool.NullPoolItem>());
                        this._poolCreator.Add(T, mth.MakeGenericMethod(T));
                    }
                }
            }

            this._componentTypes.Add(guid.Value, T);
            return guid.Value;
        }

        #endregion
    }
}
